﻿using Fuse8.ContentManagementFramework.Administration.Models;
using Fuse8.ContentManagementFramework.BizFacade.Services;
using Fuse8.ContentManagementFramework.Domain;
using Fuse8.ContentManagementFramework.Domain.Special;
using Fuse8.ContentManagementFramework.SystemFacade;
using Fuse8.ContentManagementFramework.Web.ApplicationRuntime;
using Fuse8.ContentManagementFramework.Web.ApplicationRuntime.ContentModuleModels;
using Fuse8.ContentManagementFramework.Web.Etc;
using Fuse8.ContentManagementFramework.Web.MvcPowered;
using Fuse8.DomainFramework.BusinessService;
using Fuse8.DomainFramework.Common.Exceptions;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;

namespace Fuse8.ContentManagementFramework.Administration.Controllers
{
    public class CmsPageController : CmsAdminWorkflowController<CmsPage>
    {
        #region Consts

        public const string SelectedPageId = "page";
        public const string SelectedRegionId = "region";

        #endregion Consts

        #region Fields

        private static Regex _pageUrlRegex = new Regex(@"/($|[0-9a-zA-Z])([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&amp;%\$#_]*)?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
        private static Regex _pageControlRegionRegex = new Regex(@"/?region=([0-9]*)", RegexOptions.Compiled | RegexOptions.IgnoreCase);

        private CmsPageService _pageService;
        private CmsPageControlService _pageControlService;
        private CmsTemplateService _templateService;

        #endregion Fields

        #region Properties

        protected virtual CmsPageService PageService
        {
            get
            {
                if (_pageService == null)
                {
                    _pageService = new CmsPageService();
                }

                return _pageService;
            }
        }

        protected virtual CmsPageControlService PageControlService
        {
            get
            {
                if (_pageControlService == null)
                {
                    _pageControlService = new CmsPageControlService();
                }

                return _pageControlService;
            }
        }

        protected virtual CmsTemplateService TemplateService
        {
            get
            {
                if (_templateService == null)
                {
                    _templateService = new CmsTemplateService();
                }

                return _templateService;
            }
        }

        protected override CmsEntityRevisionService<CmsPage> EntityRevisionService
        {
            get
            {
                return PageService;
            }
        }

        #endregion Properties

        public virtual ActionResult Index(bool? isPublished = null, string pageUrlFilter = null)
        {
            IEnumerable<CmsPage> data = null;

            int pageId = 0;

            int.TryParse(Request.QueryString[SelectedPageId], out pageId);

            ViewBag.AllCount = 0;
            ViewBag.PublishedCount = 0;
            ViewBag.DraftCount = 0;

            if (pageId > 0)
            {
            }
            else
            {
                var rootPage = PageService.RootPage;

                data = rootPage != null ?
                    PageService.GetChildPages(rootPage.Id, true, rootPage.PageLevel + 1) :
                    new List<CmsPage>();

                if (!string.IsNullOrWhiteSpace(pageUrlFilter))
                {
                    pageUrlFilter = pageUrlFilter.Trim();

                    data = data.Where(e => !string.IsNullOrEmpty(e.PageUrl)
                        && e.PageUrl.IndexOf(pageUrlFilter, StringComparison.InvariantCultureIgnoreCase) >= 0);
                }

                var counts = PageService.GetCountsForDifferentStatusPages(rootPage.Id);

                ViewBag.AllCount = counts.All;
                ViewBag.PublishedCount = counts.Published;
                ViewBag.DraftCount = counts.Drafts;

                if (isPublished.HasValue)
                    data = data.Where(e => e.IsPublished == isPublished.Value);
            }
            ViewBag.IsPublished = isPublished;
            ViewBag.PageUrlFilter = pageUrlFilter;

            return View(data);
        }

        public virtual ActionResult Add()
        {
            ViewData["type"] = CmsConstants.PageTypeParameter.ToLower();
            ViewBag.CurrentTemplateRegion = 0;

            var defaultTemplate = TemplateService.DefaultTemplate;
            var rootPage = PageService.RootPage;

            return View(
                new CmsAdminPageModel(
                    new CmsPage
                    {
                        PageBehaviourTypeKey = (int)CmsPageBehaviourTypeKind.Content,
                        PageTypeKey = (int)defaultTemplate.TemplateType.Convert(),
                        Template = defaultTemplate,
                        ParentPage = rootPage,
                        IsVisible = true,
                        IsPublished = true
                    }));
        }

        [HttpPost]
        public virtual ActionResult Add(CmsAdminPageModel model)
        {
            ActionResult result = null;

            if (model != null)
            {
                if (ModelState.IsValid)
                {
                    var convertToPage = model.ConvertToPage(false);
                    if (!convertToPage.IsUrlRequired || model.PageUrl != null && _pageUrlRegex.IsMatch(model.PageUrl))
                    {
                        var template = TemplateService.FindOne(model.TemplateId);
                        if (template != null)
                        {
                            model.PageTypeKey = (int)template.TemplateType.Convert();
                            if (convertToPage.IsUrlRequired)
                            {
                                model.PageUrl = CreatePageUrlFromModel(model.PageUrl);
                            }
                            else
                            {
                                model.PageUrl = "";
                            }
                            model.UseFileCache = false; //TODO: remove this

                            using (var transaction = new BusinessServiceTransaction<CmsPage>())
                            {
                                try
                                {
                                    if (!model.ParentPageId.HasValue)
                                    {
                                        var rootPage = PageService.RootPage;
                                        if (rootPage != null)
                                        {
                                            model.ParentPageId = rootPage.Id;
                                        }
                                    }

                                    var createdPage = model.ConvertToPage(false);

                                    createdPage.IsLatestCompletedRevision = !CmsConfiguration.IsWorkflowEnabled;

                                    PageService.Add(createdPage);

                                    SaveRedirectSettings(createdPage);

                                    ClearAllDependencies(true);

                                    transaction.Commit();

                                    CmsRoute.UpdateRoutes();

                                    result = RedirectToAction("Edit", new { id = createdPage.Id });
                                }
                                catch (ValidationException ex)
                                {
                                    ModelState.AddModelError("PageUrl", ex.Message);
                                }
                            }
                        }
                        else
                        {
                            result = RedirectToAction("Index"); //TO DO: think about
                        }
                    }
                    else
                    {
                        ModelState.AddModelError("PageUrl", "Page url isn't valid");
                    }
                }
            }

            return result ?? View(new CmsAdminPageModel(model));
        }

        public virtual ActionResult Edit(int id)
        {
            ViewData["type"] = CmsConstants.PageTypeParameter.ToLower();

            int templateRegionId;
            int.TryParse(Request.QueryString[SelectedRegionId], out templateRegionId);
            ViewBag.CurrentTemplateRegion = templateRegionId;

            var data = PageService.FindOne(id);

            if (data != null)
            {
                data.PageControls.SaveMemento(GetMementoKey(id));
            }

            return data != null
                ? data.IsLatestRevision.GetValueOrDefault(false)
                    ? View(new CmsAdminPageModel(data, templateRegionId)) as ActionResult
                    : View("View", new CmsAdminPageModel(data, templateRegionId))
                : RedirectToAction("Index"); ;
        }

        [HttpPost]
        [ValidateInput(false)]
        public virtual ActionResult Edit(CmsAdminPageModel model)
        {
            // Todo This method is obviously too long and needs refactoring: move logic to PageService/PageManager

            ActionResult result = null;

            CmsPage editedPage = null;

            CmsPage workingPage = PageService.FindOne(model.Id);

            // if we're just reverting to older revision - no validation needed. We just create a new revision based on the old one.
            if (!string.IsNullOrEmpty(model.MakeNewRevision))
            {
                result = MakeNewRevision(workingPage);
            }
            else // otherwise (i.e. in case of simple page editing) we start the proper validation process
            {
                CheckIfSomeoneElseEditedThePage(model);

                if (ModelState.IsValid) CheckAvailablePageKeyOtherPages(model); // no need to check for duplicate page key if the above check is not passed.

                if (ModelState.IsValid)
                {
                    int templateRegionId = 0;

                    if (workingPage != null)
                    {
                        if (!string.IsNullOrEmpty(model.Workflow))
                        {
                            result = StartWorkflow(model, workingPage);
                        }
                        else
                        {
                            //TODO: rewrite this - idea (add to model)
                            var selectedRegionMatch = _pageControlRegionRegex.Match(Request.UrlReferrer.Query);
                            if (selectedRegionMatch.Success)
                            {
                                int.TryParse(Request.UrlReferrer.Query.Replace(string.Format("?{0}=", SelectedRegionId), string.Empty), out templateRegionId);
                            }

                            if (templateRegionId == 0)
                            {
                                templateRegionId = workingPage.Template.TemplateRegions.Any() ?
                                    workingPage.Template.TemplateRegions.Select(p => p.Id).FirstOrDefault() :
                                    0;
                            }

                            bool isUrlChanged = !workingPage.PageUrl.Equals(model.PageUrl, StringComparison.Ordinal);

                            model.UseFileCache = false; //TODO: remove this
                            var convertToPage = model.ConvertToPage();
                            if (convertToPage.IsUrlRequired)
                            {
                                model.PageUrl = CreatePageUrlFromModel(model.PageUrl);
                            }
                            else
                            {
                                model.PageUrl = "";
                            }

                            //editedPage = model.ConvertToPage();
                            editedPage = convertToPage;
                            editedPage.PublicPageId = workingPage.PublicPageId;
                            editedPage.CreatedBy = workingPage.CreatedBy;
                            editedPage.DateCreated = workingPage.DateCreated;

                            if (!string.IsNullOrEmpty(model.Preview))
                            {
                                result = LivePreview(editedPage, workingPage);
                            }
                            else
                            {
                                using (var transaction = new BusinessServiceTransaction<CmsPage>()
                                    .With<CmsPageControl>().With<CmsEntityMeta>().With<CmsEntityMetaInformation>())
                                {
                                    try
                                    {
                                        if (CmsConfiguration.IsWorkflowEnabled)
                                        {
                                            //todo: published should not depend on workflow?
                                            editedPage.IsPublished = false;
                                            editedPage.IsLatestCompletedRevision = false;
                                        }

                                        PageService.Save(editedPage);

                                        // as saving page creates new revision we need to get this new revision before saving controls and metas
                                        editedPage = PageService.FindOne(editedPage.Id);

                                        if (convertToPage.BehaviourPageType == CmsPageBehaviourTypeKind.Redirect)
                                        {
                                            editedPage.RedirectSetting = convertToPage.RedirectSetting;
                                            SaveRedirectSettings(editedPage);
                                        }

                                        if (workingPage.TemplateId == model.TemplateId)
                                        {
                                            SavePageControls(templateRegionId, editedPage, model.Id);
                                        }

                                        ChangeLocation(model, editedPage);

                                        ChangeParent(model, editedPage, workingPage);

                                        if (model.Metas != null)
                                        {
                                            foreach (var meta in model.Metas)
                                            {
                                                editedPage.SetMetaValue(meta.Name, meta.Value, Type.GetType(meta.Type), true);
                                            }
                                        }

                                        ClearAllDependencies(isUrlChanged);

                                        transaction.Commit();

                                        CmsRoute.UpdateRoutes();
                                    }
                                    catch (ValidationException ex)
                                    {
                                        ControllerContext.HttpContext.Response.StatusCode = 500;
                                        return Content(ex.Message, "text/html", Encoding.UTF8);
                                    }
                                }
                            }
                        }

                        if (!string.IsNullOrEmpty(model.Apply))
                        {
                            if (Request.IsAjaxRequest())
                            {
                                result = editedPage.Template.HasRegions
                                    ? Content(string.Format("/admin/cmspage/edit/{0}?region={1}", editedPage.Id,
                                        editedPage.Template.TemplateRegions.First().Id))
                                    : Content(string.Format("/admin/cmspage/edit/{0}", editedPage.Id));
                            }
                            else
                            {
                                result = RedirectToAction("Edit", new { editedPage.Id, region = templateRegionId });
                            }
                        }
                    }

                    // Сhange the values ​​in TemplateRegionId for the same TemplateRegionName if template changed.
                    if (workingPage.TemplateId != model.TemplateId && editedPage != null)
                    {
                        PageControlService.ChangeTemplateRegions(workingPage.Id, editedPage.Id);
                    }
                }
                else
                {
                    string error = ModelState.Select(p => p.Value)
                        .Where(p => p.Errors.Any())
                        .Select(p => p.Errors
                            .Select(q => q.ErrorMessage)
                            .Aggregate(
                            (working, next) =>
                            {
                                return string.Format("{0} {1}", working, next);
                            }))
                            .Aggregate(
                            (working, next) =>
                            {
                                return string.Format("{0} {1} {2}", working, Environment.NewLine, next);
                            });

                    ControllerContext.HttpContext.Response.StatusCode = 500;
                    result = Content(error, "text/html", Encoding.UTF8);
                }
            }

            return result ?? RedirectToAction("Index");
        }

        protected void ChangeParent(CmsAdminPageModel model, CmsPage editedPage, CmsPage workingPage)
        {
            if (model.ParentPageId.HasValue && model.ParentPageId > 0 && model.ParentPageId != workingPage.ParentPageId)
            {
                PageService.ReparentPage(editedPage.Id, model.ParentPageId.Value);
            }
        }

        protected void ChangeLocation(CmsAdminPageModel model, CmsPage editedPage)
        {
            if ((model.PageLocation > 0) && (model.Id != model.PageLocation))
            {
                PageService.MovePage(editedPage.Id, model.PageLocation);
            }
        }

        protected void SavePageControls(int templateRegionId, CmsPage editedPage, int oldPageId)
        {
            IEnumerable<CmsPageControl> parsedPageControls =
                ParsePageControls(editedPage, ControllerContext.HttpContext.Request.Form, oldPageId);

            var workingPageControlToDelete = templateRegionId > 0
                                                 ? editedPage.PageControls.Where(p => p.TemplateRegionId == templateRegionId)
                                                 : editedPage.PageControls;

            if (workingPageControlToDelete.Any())
            {
                foreach (var pageControl in workingPageControlToDelete)
                {
                    PageControlService.Remove(pageControl);
                }
            }
            // todo: get rid of if else
            foreach (var pageControl in parsedPageControls)
            {
                if (pageControl.CommonPageControlType == PageControlTypeKind.Content)
                {
                    PageControlService.AddPageContent(editedPage.Id, pageControl, CmsConfiguration.IsWorkflowEnabled);
                }
                else if (pageControl.CommonPageControlType == PageControlTypeKind.Snippet)
                {
                    PageControlService.AddPageSnippet(editedPage.Id, pageControl, CmsConfiguration.IsWorkflowEnabled);
                }
                else if (pageControl.CommonPageControlType == PageControlTypeKind.MvcPartial)
                {
                    PageControlService.AddPagePartialControl(editedPage.Id, pageControl.TemplateRegionId,
                                                             pageControl.PageControlViewValue, pageControl.PageControlModelValue,
                                                             CmsConfiguration.IsWorkflowEnabled, pageControl.PageControlOrder, pageControl.CommonPageControlType);
                }
                else if (pageControl.CommonPageControlType == PageControlTypeKind.MvcAction || pageControl.CommonPageControlType == PageControlTypeKind.ContentModule)
                {
                    PageControlService.AddPagePartialControl(editedPage.Id, pageControl.TemplateRegionId,
                                                             pageControl.PageControlViewValue, pageControl.PageControlModelValue,
                                                             CmsConfiguration.IsWorkflowEnabled, pageControl.PageControlOrder,
                                                             pageControl.CommonPageControlType);
                }
                else if (pageControl.CommonPageControlType == PageControlTypeKind.ModulePart)
                {
                    PageControlService.AddModule(editedPage.Id, pageControl, CmsConfiguration.IsWorkflowEnabled);
                }
            }
        }

        protected virtual ActionResult LivePreview(CmsPage editedPage, CmsPage workingPage)
        {
            ActionResult result;
            IEnumerable<CmsPageControl> parsedPageControls =
                ParsePageControls(workingPage, ControllerContext.HttpContext.Request.Form, workingPage.Id);

            CmsContext.ContextPage = editedPage;

            var contextPageTemplate = editedPage.Template;

            var contextPageTemplateRegions = contextPageTemplate.TemplateRegions;

            foreach (var region in contextPageTemplateRegions)
            {
                if (parsedPageControls.Any(p => p.TemplateRegionId == region.Id))
                {
                    region.RegionControls = parsedPageControls
                        .Where(p => p.TemplateRegionId == region.Id)
                        .ToList(); //to make immediate effect
                }
            }

            result = new CmsViewResult
                         {
                             IsForPreview = true,
                             TemplateType = contextPageTemplate.TemplateType,
                             ViewName = editedPage.PageName,
                             MasterName = contextPageTemplate.TemplateUrl,
                             ViewData = ViewData,
                             TempData = TempData
                         };
            return result;
        }

        protected static void SaveRedirectSettings(CmsPage page)
        {
            if (page.BehaviourPageType == CmsPageBehaviourTypeKind.Redirect)
            {
                if (!page.RedirectSetting.PublicPageIdToRedirect.HasValue &&
                    string.IsNullOrEmpty(page.RedirectSetting.UrlToRedirect))
                {
                    throw new ValidationException("Please either enter Url to redirect or select page");
                }

                page.SetMetaValue(CmsPage.RedirectPropertyKey, page.RedirectSetting, true);
            }
        }

        protected virtual ActionResult StartWorkflow(CmsAdminPageModel model, CmsPage workingPage)
        {
            ActionResult result;
            result = StartWorkflow(workingPage, model.DateApprovalDeadline, model.DateReviewDeadline, model.WorkflowTransitionId,
                                   model.WorkflowNotes, model.AssignedUser);

            if (model.SendNotification && model.AssignedUser.HasValue)
            {
                var assignedUser = UserInformationService.GetUserInformationForManagement(model.AssignedUser.Value);

                if ((assignedUser != null) && (assignedUser.LoggedInUser != null))
                {
                    EmailService.Send(
                        new CmsEntitySentForApprovalEmail(
                            new
                                {
                                    UserEmail = assignedUser.LoggedInUser.UserEmail,
                                    UserName = assignedUser.LoggedInUser.UserName,
                                    Url = Url.Action("workflowitem", "cmspage", new { id = model.Id })
                                }));
                }
            }

            //TO DO: redirect to first workflow page
            return result;
        }

        protected virtual ActionResult MakeNewRevision(CmsPage workingPage)
        {
            ActionResult result = null;
            using (var transaction = new BusinessServiceTransaction<CmsPage>())
            {
                try
                {
                    workingPage.IsLatestRevision = true;
                    workingPage.IsLatestCompletedRevision = true;

                    if (CmsConfiguration.IsWorkflowEnabled)
                    {
                        //todo: published should not depend on workflow?
                        workingPage.IsPublished = false;
                        workingPage.IsLatestCompletedRevision = false;
                    }

                    PageService.Save(workingPage);

                    transaction.Commit();
                }
                catch (ValidationException ex)
                {
                    ControllerContext.HttpContext.Response.StatusCode = 500;
                    result = Content(ex.Message, "text/html", Encoding.UTF8);
                }
            }

            ClearAllDependencies(true);

            return result;
        }

        public virtual ActionResult View(int id, int latestRevisionId)
        {
            ViewData["type"] = "page"; //TO DO : move to settings or something like that

            int templateRegionId = 0;
            int.TryParse(Request.QueryString[SelectedRegionId], out templateRegionId);
            ViewBag.CurrentTemplateRegion = templateRegionId;

            var data = PageService.FindOne(id);

            return data != null ?
                View(new CmsAdminPageModel(data, templateRegionId) { LatestPageId = latestRevisionId }) as ActionResult :
                RedirectToAction("Index");
        }

        public virtual ActionResult Copy(int id)
        {
            var newPage = PageService.Copy(id);
            if (newPage == null)
                return RedirectToAction("Index");

            ClearAllDependencies(true);
            return RedirectToAction("Edit", new { id = newPage.Id });
        }

        public virtual ActionResult WorkflowPreview(int id)
        {
            ViewData["type"] = "page"; //TO DO : move to settings or something like that

            int templateRegionId = 0;
            int.TryParse(Request.QueryString[SelectedRegionId], out templateRegionId);
            ViewBag.CurrentTemplateRegion = templateRegionId;

            var data = PageService.FindOne(id);

            return data != null ?
                PartialView(new CmsAdminPageModel(data, templateRegionId)) as ActionResult :
                new EmptyResult();
        }

        public virtual ActionResult Preview(int id)
        {
            ActionResult result = null;

            if (id > 0)
            {
                var data = PageService.PreviewPage(id);

                if (data != null)
                {
                    CmsContext.ContextPage = data;

                    result = new CmsViewResult
                    {
                        TemplateType = data.Template.TemplateType,
                        ViewName = data.PageName,
                        MasterName = data.Template.TemplateUrl,
                        ViewData = ViewData,
                        TempData = TempData
                    };
                }
            }

            return result ?? new EmptyResult();
        }

        public virtual ActionResult PageControls(int pageId, int templateRegionId, bool? editable)
        {
            IEnumerable<CmsPageControl> data = null;

            ViewBag.Editable = editable.GetValueOrDefault(true);

            var page = PageService.FindOne(pageId);

            if (page != null)
            {
                data = page.PageControls.Where(p => p.TemplateRegionId == templateRegionId);
            }

            return PartialView(data ?? new List<CmsPageControl>());
        }

        [HttpPost]
        public virtual ActionResult AddContent(int pageId, int templateRegionId)
        {
            ActionResult result = null;

            bool renderMoveUp = false;

            var control = CreatePageControl(PageControlTypeKind.Content, pageId, templateRegionId, null, null, out renderMoveUp);

            if (control != null)
            {
                result = PartialView(
                    "PageControlItem",
                    new
                    {
                        Model = control,
                        RenderMoveUp = renderMoveUp,
                        RenderMoveDown = false,
                        IsMinState = true
                    }.ToExpando());
            }

            ViewBag.Editable = true;

            return result ?? new EmptyResult();
        }

        [ValidateInput(false)]
        public ActionResult AddTelerikEditor(string clientId, string name, string content)
        {
            return PartialView("TelerikEditor", new { Content = HttpUtility.HtmlDecode(content), ClientId = clientId, Name = name }.ToExpando());
        }

        [HttpPost]
        public virtual ActionResult AddContentModule(int pageId, int templateRegionId)
        {
            ActionResult result = null;

            bool renderMoveUp = false;

            var storedModel = new CmsMvcPartialModel
                                  {
                                      ActionController = "CmsContentModule",
                                      ActionName = "RenderContentModule",
                                      PartialModelType = typeof(ContentModuleModel).FullName,
                                      PartialAdministrationViewName = "ContentModuleAdmin"
                                  }
                .MakeItPacked();

            var control = CreatePageControl(PageControlTypeKind.ContentModule, pageId, templateRegionId, null,
                                            storedModel.ViewValue, out renderMoveUp);
            if (control != null)
            {
                result = PartialView(
                    "PageControlItem",
                    new
                        {
                            Model = control,
                            RenderMoveUp = renderMoveUp,
                            RenderMoveDown = false,
                            IsMinState = true
                        }.ToExpando());
            }

            ViewBag.Editable = true;

            return result ?? new EmptyResult();
        }

        [HttpPost]
        public virtual ActionResult AddSnippet(int pageId, int templateRegionId, int contentId)
        {
            ActionResult result = null;

            bool renderMoveUp = false;

            var control = CreatePageControl(PageControlTypeKind.Snippet, pageId, templateRegionId, null, null, out renderMoveUp);

            if (control != null)
            {
                control.PageControlContentId = contentId;
                var content = new CmsContentService().FindOne(contentId);
                if (content != null)
                {
                    control.PageControlContent = content.ContentBody;
                }
                result = PartialView(
                    "PageControlItem",
                    new
                    {
                        Model = control,
                        RenderMoveUp = renderMoveUp,
                        RenderMoveDown = false,
                        IsMinState = true
                    }.ToExpando());
            }

            ViewBag.Editable = true;

            return result ?? new EmptyResult();
        }

        [HttpPost]
        public virtual ActionResult AddPartialView(CmsMvcPartialViewModel model)
        {
            ActionResult result = null;

            bool renderMoveUp = false;

            if (model != null)
            {
                var storedModel = new CmsMvcPartialModel
                {
                    PartialViewName = model.PartialViewName,
                    PartialAdministrationViewName = model.PartialAdministrationViewName,
                    PartialModelType = model.PartialModelType
                }
                .MakeItPacked();

                var control = CreatePageControl(PageControlTypeKind.MvcPartial, model.PageId, model.TemplateRegionId, null, storedModel.ViewValue, out renderMoveUp, model.Caption);
                if (control != null)
                {
                    result = PartialView(
                        "PageControlItem",
                        new
                        {
                            Model = control,
                            RenderMoveUp = renderMoveUp,
                            RenderMoveDown = false,
                            IsMinState = true
                        }.ToExpando());
                }
            }

            ViewBag.Editable = true;

            return result ?? new EmptyResult();
        }

        [HttpPost]
        public virtual ActionResult AddActionView(CmsMvcPartialViewModel model)
        {
            ActionResult result = null;

            bool renderMoveUp = false;

            if (model != null)
            {
                var storedModel = new CmsMvcPartialModel
                {
                    ActionController = model.ActionController,
                    ActionName = model.ActionName,
                    PartialModelType = model.PartialModelType
                }
                .MakeItPacked();

                var control = CreatePageControl(PageControlTypeKind.MvcAction, model.PageId, model.TemplateRegionId, null, storedModel.ViewValue, out renderMoveUp, model.Caption);
                if (control != null)
                {
                    result = PartialView(
                        "PageControlItem",
                        new
                        {
                            Model = control,
                            RenderMoveUp = renderMoveUp,
                            RenderMoveDown = false,
                            IsMinState = true
                        }.ToExpando());
                }
            }

            ViewBag.Editable = true;

            return result ?? new EmptyResult();
        }

        [HttpPost]
        public virtual ActionResult AddModule(int moduleId, int pageId, int templateRegionId, string caption)
        {
            ActionResult result = null;

            bool renderMoveUp = false;

            var control = CreatePageControl(PageControlTypeKind.ModulePart, pageId, templateRegionId, moduleId, null, out renderMoveUp, caption);
            if (control != null)
            {
                result = PartialView(
                    "PageControlItem",
                    new
                    {
                        Model = control,
                        RenderMoveUp = renderMoveUp,
                        RenderMoveDown = false,
                        IsMinState = true
                    }.ToExpando());
            }

            ViewBag.Editable = true;

            return result ?? new EmptyResult();
        }

        public virtual ActionResult PageHistory(int id)
        {
            var data = PageService.FindOne(id);

            return data != null ?
                View(new CmsAdminPageModel(data) { LatestPageId = id }) as ActionResult :
                RedirectToAction("Index");
        }

        public virtual ActionResult GetChildren(int id)
        {
            ActionResult result = null;

            var parentPage = PageService.FindOne(id);

            if (parentPage != null)
            {
                var data = PageService.GetChildPages(parentPage.Id, false, 1);

                result = PartialView("PageGrid", new { Pages = data, UseHeader = false }.ToExpando());

                PageTreeHistory.Current.Open(parentPage.Id);
            }

            return result ?? new EmptyResult();
        }

        public ActionResult ChangePageTree(int pageId, bool isOpen)
        {
            if (isOpen)
            {
                PageTreeHistory.Current.Open(pageId);
            }
            else
            {
                PageTreeHistory.Current.Close(pageId);
            }
            return Content("1");
        }

        public virtual ActionResult Move(int id, int moveToId)
        {
            PageService.MovePage(id, moveToId);

            return RedirectToAction("Index");
        }

        [HttpDelete]
        public virtual ActionResult DeletePageControl(int pageId, int templateRegionId, int pageControlId)
        {
            ActionResult result = null;

            var workingPage = PageService.FindOne(pageId);
            if (workingPage != null)
            {
                var existingPageControls = (workingPage.PageControls.RestoreMemento(GetMementoKey(pageId)) ?? new List<CmsPageControl>()) as IList<CmsPageControl>;

                var workingPageControl = existingPageControls.FirstOrDefault(p => p.Id == pageControlId);
                if (workingPageControl != null)
                {
                    existingPageControls.Remove(workingPageControl);

                    workingPage.PageControls = existingPageControls;

                    workingPage.PageControls.SaveMemento(GetMementoKey(pageId));
                }

                result = Json(new { success = 1, total = existingPageControls.Count(p => p.TemplateRegionId == templateRegionId) });
            }

            return result ?? Json(new { success = 0, total = 0 });
        }

        protected override IEnumerable<CmsAdminRevisionViewModel> PopulateRevisionModel(int entityId)
        {
            ViewBag.ViewUrl = "/admin/cmspage/view";

            var currentPage = PageService.FindOne(entityId);

            var data = currentPage != null ?
                PageService.FindAllRevisions(currentPage) :
                null;

            return data != null ?
                data.Select(
                    p => new CmsAdminRevisionViewModel(p)
                    {
                        Id = p.Id,
                        LatestRevisionId = entityId,
                        Author = string.IsNullOrEmpty(p.UserLastUpdatedBy) ? p.UserCreatedBy : p.UserLastUpdatedBy
                    }) :
                new List<CmsAdminRevisionViewModel>().AsEnumerable();
        }

        protected override IEnumerable<CmsWorkflow> PopulateWorkflowNotesModel(int entityId)
        {
            ViewBag.ViewUrl = "/admin/cmspage/view";

            var currentPage = PageService.FindOne(entityId);

            var data = currentPage != null ?
                WorkflowService.FindAllWorkflowNotes(currentPage) :
                null;

            return data ?? new List<CmsWorkflow>();
        }

        protected override void OnWorkflowProcessed(WorkflowEventArgs<CmsPage> e)
        {
            if (e.Entity.IsLatestCompletedRevision.GetValueOrDefault(false))
            {
                ClearAllDependencies(true);
            }

            base.OnWorkflowProcessed(e);
        }

        protected override bool _Delete(int id)
        {
            PageService.Remove(id);

            ClearAllDependencies(true);

            return true;
        }

        protected virtual IEnumerable<CmsPageControl> ParsePageControls(CmsPage workingPage, NameValueCollection elements, int pageId)
        {
            var result = new List<CmsPageControl>();

            if (workingPage != null)
            {
                var existingPageControls = workingPage.PageControls;
                var recievedPageControls = workingPage.PageControls.RestoreMemento(GetMementoKey(pageId));

                var pageControlElements = new List<PageControlElement>();
                for (int i = 0; i < elements.Count; i++)
                {
                    var elementKeys = elements.GetKey(i).Split(new char[] { '_' }, StringSplitOptions.RemoveEmptyEntries); //TO DO: move this to settings
                    if (elementKeys.Length == 2)
                    {
                        int pageControlId = 0;
                        if (int.TryParse(elementKeys[1], out pageControlId))
                        {
                            if (!pageControlElements.Any(e => e.Id == pageControlId))
                            {
                                pageControlElements.Add(new PageControlElement(pageControlId));
                            }

                            pageControlElements.First(e => e.Id == pageControlId).Add(elementKeys[0], elements[i]);
                        }
                    }
                }

                var modifiedPageControls = recievedPageControls.Where(p => pageControlElements.Any(e => e.Id == p.Id))
                    .OrderBy(p => pageControlElements.FindIndex(e => e.Id == p.Id)).ToList();

                var order = 1;
                modifiedPageControls.ForEach(
                    (p) =>
                    {
                        var concretePageControlElements = pageControlElements.Find(e => e.Id == p.Id);

                        if (!string.IsNullOrEmpty(concretePageControlElements["AdminCaption"]))
                        {
                            p.AdminCaption = concretePageControlElements["AdminCaption"];
                        }

                        if (!string.IsNullOrWhiteSpace(concretePageControlElements["PageControlContentId"]))
                        {
                            //page content
                            p.PageControlContentId = int.Parse(concretePageControlElements["PageControlContentId"]);
                            p.PageControlContent = concretePageControlElements["PageControlContent"]; //HttpContext.Server.HtmlDecode(concretePageControlElements["PageControlContent"]);
                        }
                        else if (!string.IsNullOrWhiteSpace(concretePageControlElements["ModuleId"]))
                        {
                            //page module
                            p.ModuleId = int.Parse(concretePageControlElements["ModuleId"]);
                            p.SaveCmsModuleParameters(HttpContext, concretePageControlElements);
                        }
                        else
                        {
                            //another type of page control
                            p.SaveCmsMvcPartialControlParameters(HttpContext, concretePageControlElements);
                        }

                        p.PageControlOrder = order++;
                    });

                result = modifiedPageControls;
            }

            return result;
        }

        protected virtual CmsPageControl CreatePageControl(PageControlTypeKind controlType, int pageId, int templateRegionId, int? moduleId, string pageControlViewValue, out bool renderMoveUp, string caption = null)
        {
            CmsPageControl result = null;

            renderMoveUp = false;

            var workingPage = PageService.FindOne(pageId);
            if (workingPage != null)
            {
                var existingPageControls = (workingPage.PageControls.RestoreMemento(GetMementoKey(pageId)) ?? new List<CmsPageControl>()) as IList<CmsPageControl>;

                renderMoveUp = existingPageControls.Count(p => p.TemplateRegionId == templateRegionId) > 0;

                int createdControlId = existingPageControls.Any() ? existingPageControls.Min(p => p.Id) - 1 : -1;

                if (createdControlId > 0)
                {
                    createdControlId = -1;
                }

                result = new CmsPageControl
                {
                    Id = createdControlId,
                    PageControlTypeKey = (int)controlType,
                    PageControlOrder =
                        existingPageControls.Any() ?
                            existingPageControls.Max(p => p.PageControlOrder) + 1 :
                            1,
                    PageId = pageId,
                    TemplateRegionId = templateRegionId,
                    ModuleId = moduleId,
                    PageControlViewValue = pageControlViewValue,
                    AdminCaption = caption
                };

                if (controlType == PageControlTypeKind.Content)
                {
                    result.PageControlContentId = existingPageControls.Any() ?
                        existingPageControls.Max(p => p.PageControlContentId.GetValueOrDefault(0)) + 1 :
                        0;
                }

                existingPageControls.Add(result);

                workingPage.PageControls = existingPageControls;

                workingPage.PageControls.SaveMemento(GetMementoKey(pageId));
            }

            return result;
        }

        protected static void ClearAllDependencies(bool urlChanged)
        {
            //TO DO: remove this method - use usual cache dependency when it will be confugured and applied
            if (urlChanged)
            {
                CmsRoute.ClearByDependency();
            }
        }

        protected static string CreatePageUrlFromModel(string url)
        {
            return string.Format("/{0}", (url ?? string.Empty).TrimStart(new char[] { '/' }));
        }

        private class PageControlElement : NameValueCollection
        {
            public PageControlElement(int pageControlId)
            {
                Id = pageControlId;
            }

            public int Id { get; private set; }
        }

        private static string GetMementoKey(int id)
        {
            // TODO possibly get rid of this whole "Memento" stuff
            return string.Format("CmsPage_{0}", id);
        }

        public override ActionResult EntityAnnotations(int id)
        {
            return EntityAnnotations(id, "Page");
        }

        private void CheckAvailablePageKeyOtherPages(CmsAdminPageModel model)
        {
            if (!string.IsNullOrEmpty(model.PageKey))
            {
                var pages = PageService.GetPagesByPageKey(model.PageKey).Where(x => x.Id != model.Id);

                if (!model.IsSaveConfirmation && pages.Any())
                {
                    ModelState.AddModelError("", "Specified PageKey already exists. Identical PageKeys are usefull if your site is multilanguage or if you're creating several versions of the same page, published in different time periods. Otherwise having identical PageKeys is a mistake. Continue? <a href='#' class='js-submit-page-edit'>Yes</a>");//" | <a href='#' data-bind='click: function() { viewEventModel.removeAllEvents() }'>No</a>");
                }
            }
        }

        private void CheckIfSomeoneElseEditedThePage(CmsAdminPageModel model)
        {
            var page = PageService.FindByPublicId(model.PublicPageId);

            if (!model.IsSaveConfirmation && page.Id > model.Id)
            {
                ModelState.AddModelError("", "Someone else edited the page while you were editing it. Would you like to save your changes as a new latest revision? You will be able to review the other person's change in the page history and revert the page to that revision if needed. Continue? <a href='#' class='js-submit-page-edit'>Yes</a>");
            }
        }

        public ActionResult Reconnect()
        {
            return Content("1");
        }
    }
}